using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;

using Storyteller.Framework.Math;

namespace Storyteller.Framework.Graphics
{
	public class Bone
	{
		public string Name { get; set; }
		
		public Vector2 Position { get; set; }
		public float Rotation { get; set; }
		
		public bool VerticalFlip { get; set; }
		public bool HorizontalFlip { get; set; }
		
		public int TextureIndex { get; set; }
		public int ParentIndex { get; set; }
		
		public bool Visible { get; set; }
	}
	
	public class BoneTransform
	{
		public Vector2 Translation { get; set; }
		public float Rotation { get; set; }
		public Matrix4 Transform { get; set; }
	}
	
	public class BoneTransitionState
	{
		public Vector2 Translation { get; set; }
		public float Rotation { get; set; }
	}
	
	public class Keyframe
	{
		public List<Bone> Bones { get; set; }
		public List<Bone> UpdateOrderBones { get; set; }
		
		public bool VerticalFlip { get; set; }
		public bool HorizontalFlip { get; set; }
		
		public int FrameNumber { get; set; }
		public float FrameTime { get; set; }
		
		public string FrameTrigger { get; set; }
		public string FrameTag { get; set; }
	}
	
	public class DeminaAnimation
	{
		public List<Keyframe> Keyframes { get; set; }
		public List<TextureRegion> TextureRegions { get; set; }
		
		public Texture Texture { get; set; }
		
		public int FrameRate { get; set; }
		public int LoopFrame { get; set; }
		public float LoopTime { get; set; }
		
		public DeminaAnimation(string filePath, Texture texture, TextureDictionary textureDictionary)
		{
			XDocument xmlDocument = XDocument.Load(filePath);
			
			Texture = texture;
			
			FrameRate = int.Parse((from f in xmlDocument.Descendants("FrameRate") select f).First().Value, CultureInfo.InvariantCulture);
			LoopFrame = int.Parse((from f in xmlDocument.Descendants("LoopFrame") select f).First().Value, CultureInfo.InvariantCulture);
			float invFrameRate = 1.0f / FrameRate;
			LoopTime = invFrameRate * LoopFrame;

			TextureRegions = (from frame in xmlDocument.Descendants("Texture")
			                  select textureDictionary.GetRegion(frame.Value)).ToList<TextureRegion>();

			Keyframes = (from key in xmlDocument.Descendants("Keyframe")
						 select new Keyframe()
						 {
							 UpdateOrderBones = new List<Bone>(),
							 VerticalFlip = key.Attribute("vflip").Value == "True",
							 HorizontalFlip = key.Attribute("hflip").Value == "True",
							 FrameNumber = int.Parse(key.Attribute("frame").Value, CultureInfo.InvariantCulture),
							 FrameTime = invFrameRate * int.Parse(key.Attribute("frame").Value, CultureInfo.InvariantCulture),
							 FrameTrigger = key.Attribute("trigger").Value,
							 FrameTag = key.Attribute("tag").Value,
							 Bones = (from b in key.Descendants("Bone")
									  select new Bone()
									  {
										  Name = b.Attribute("name").Value,
										  Visible = b.Element("Hidden").Value != "True",
										  VerticalFlip = b.Element("TextureFlipVertical").Value == "True",
										  HorizontalFlip = b.Element("TextureFlipHorizontal").Value == "True",
										  TextureIndex = int.Parse(b.Element("TextureIndex").Value, CultureInfo.InvariantCulture),
										  ParentIndex = int.Parse(b.Element("ParentIndex").Value, CultureInfo.InvariantCulture),
										  Position = new Vector2(float.Parse(b.Element("Position").Element("X").Value, CultureInfo.InvariantCulture),
											  float.Parse(b.Element("Position").Element("Y").Value, CultureInfo.InvariantCulture)),
										  Rotation = float.Parse(b.Element("Rotation").Value, CultureInfo.InvariantCulture),
									  }).ToList<Bone>(),
						 }).ToList<Keyframe>();

			int firstBoneCount = -1;
			foreach (var k in Keyframes)
			{
				if (firstBoneCount == -1)
					firstBoneCount = k.Bones.Count;
				else if (firstBoneCount != k.Bones.Count)
					throw new InvalidOperationException("Keyframes must all contain the same number of bones.");
			}
		}
	}
	
		
	public class DeminaAnimationPlayer : AnimationPlayer
	{
		// FIXME: This should probably be in the base AnimationPlayer
		// and also, should it be the drawing origin as well as rotation?
		public Vector2 RotationOrigin { get; set; }

		public DeminaAnimationPlayer()
		{
			boneCount = -1;
			currentAnimation = null;
			transitioning = false;
		}

		public override void Start(string name, bool allowRestart)
		{
			if (CurrentAnimationName != name || allowRestart)
			{
				transitioning = false;
				transitionTag = "";
				
				CurrentAnimationName = name;
				currentAnimation = animations[name];
				currentAnimationTime = 0;
				currentKeyframeIndex = 0;
				
				Update(0);
			}
		}

		public override void Transition(string name, float transitionDelay)
		{
			for(int i=0; i<boneCount; i++)
			{
				transitionStates[i].Rotation = boneTransforms[i].Rotation;
				transitionStates[i].Translation = boneTransforms[i].Translation;
			}
			
			transitioning = true;
			transitionTag = "";
			transitionTime = 0;
			transitionTotalTime = transitionDelay;
			transitionKeyframe = 0;
			TransitionAnimationName = name;
			transitionAnimation = animations[name];
		}

		public void TransitionToTag(string animationName, string tag, float transitionDelay)
		{
			for (int i = 0; i < boneCount; i++)
			{
				transitionStates[i].Rotation = boneTransforms[i].Rotation;
				transitionStates[i].Translation = boneTransforms[i].Translation;
			}

			DeminaAnimation targetAnimation = animations[animationName];

			for (int i = 0; i < targetAnimation.Keyframes.Count; i++)
			{
				if (targetAnimation.Keyframes[i].FrameTag == tag)
				{
					transitioning = true;
					transitionTag = "";
					transitionTime = 0;
					transitionTotalTime = transitionDelay;
					transitionKeyframe = i;
					TransitionAnimationName = animationName;
					transitionAnimation = animations[animationName];
					break;
				}
			}
		}

		public void TransitionAtTag(string animationName, string tag, float transitionDelay)
		{
			transitionTag = tag;
			transitionTagAnimationName = animationName;
			transitionTagTime = transitionDelay;
		}

		public void TransitionAtNextTag(string animationName, float transitionDelay)
		{
			for (int frameIndex = currentKeyframeIndex + 1; frameIndex != currentKeyframeIndex; frameIndex++)
			{
				if (frameIndex >= currentAnimation.Keyframes.Count)
				{
					if (currentAnimation.LoopFrame != -1)
						frameIndex = 0;
					else
						break;
				}

				if (!string.IsNullOrEmpty(currentAnimation.Keyframes[frameIndex].FrameTag))
				{
					TransitionAtTag(animationName, currentAnimation.Keyframes[frameIndex].FrameTag, transitionDelay);
					return;
				}
			}
		}

		public override AnimationEvent Update(float deltaSeconds)
		{
			AnimationEvent animationEvent = new AnimationEvent();
			animationEvent.Complete = false;
			animationEvent.EventAvailable = false;
			animationEvent.EventName = "";
			
			if (currentAnimation == null)
				return animationEvent;
			
			int startKeyframeIndex = currentKeyframeIndex;
			
			if (transitioning)
			{
				transitionTime += deltaSeconds;
				
				if (transitionTime > transitionTotalTime)
				{
					transitioning = false;
					
					currentAnimation = transitionAnimation;
					CurrentAnimationName = TransitionAnimationName;
					// note: the next line is more correct, but setting to zero dodges errors from long delays
					//currentAnimationTime = transitionTime - transitionTotalTime;
					currentKeyframeIndex = transitionKeyframe;
					currentAnimationTime = currentAnimation.Keyframes[currentKeyframeIndex].FrameTime;
				}
			}
			else
			{
				bool reachedEnd = false;
				
				currentAnimationTime += deltaSeconds;
				
				if (currentKeyframeIndex == currentAnimation.Keyframes.Count - 1)
				{
					if (currentAnimation.LoopFrame != -1)
					{
						if (currentAnimationTime > currentAnimation.LoopTime)
						{
							currentAnimationTime -= currentAnimation.LoopTime;
							currentKeyframeIndex = 0;
						}
					}
					else
					{
						currentAnimationTime = currentAnimation.Keyframes[currentKeyframeIndex].FrameTime;
						reachedEnd = true;
					}
				}
				else
				{
					if (currentAnimationTime > currentAnimation.Keyframes[currentKeyframeIndex + 1].FrameTime)
					{
						// note: this line introduces a slight timing error, but dodges errors caused by very long delays
						currentAnimationTime = currentAnimation.Keyframes[currentKeyframeIndex + 1].FrameTime;
						currentKeyframeIndex++;
					}
				}
				
				animationEvent.Complete = reachedEnd;
			}
			
			if (currentKeyframeIndex != startKeyframeIndex)
			{
				if (!string.IsNullOrEmpty(transitionTag) && transitionTag == currentAnimation.Keyframes[currentKeyframeIndex].FrameTag)
				{
					TransitionToTag(transitionTagAnimationName, transitionTag, transitionTagTime);
					transitionTag = "";
				}
				else
				{
					animationEvent.EventName = currentAnimation.Keyframes[currentKeyframeIndex].FrameTrigger;
					animationEvent.EventAvailable = !string.IsNullOrEmpty(animationEvent.EventName);
				}
			}

			UpdateBoneTransforms();
			
			return animationEvent;
		}

		public override AnimationEvent SetTime(float seconds)
		{
			AnimationEvent animationEvent = new AnimationEvent();
			animationEvent.Complete = false;
			animationEvent.EventAvailable = false;
			animationEvent.EventName = "";
		
			if (currentAnimation == null)
				return animationEvent;
			
			if (transitioning)
			{
				transitioning = false;
				
				currentAnimation = transitionAnimation;
				CurrentAnimationName = TransitionAnimationName;
			}
			
			currentAnimationTime = seconds;
			currentKeyframeIndex = 0;
			
			int keyframeCount = currentAnimation.Keyframes.Count;
			for(int i=0; i<keyframeCount; i++)
			{
				if(currentAnimation.Keyframes[i].FrameTime > seconds)
					break;
				currentKeyframeIndex = i;
			}
			
			UpdateBoneTransforms();
			
			animationEvent.EventName = currentAnimation.Keyframes[currentKeyframeIndex].FrameTrigger;
			animationEvent.EventAvailable = !string.IsNullOrEmpty(animationEvent.EventName);
			
			return animationEvent;
		}

		public override void Draw(SpriteBatch spriteBatch, Vector2 position, Color color, float rotation, Vector2 scale, bool flipHorizontal, bool flipVertical)
		{
			if(currentAnimation == null)
				return;
			
			Matrix4 scaleMatrix = Matrix4.Scale(scale.X, scale.Y, 1);

			if (MathHelper.Xor(flipHorizontal, currentAnimation.Keyframes[currentKeyframeIndex].HorizontalFlip))
				scaleMatrix =  scaleMatrix * Matrix4.Scale(-1, 1, 1);
			if(MathHelper.Xor(flipVertical, currentAnimation.Keyframes[currentKeyframeIndex].VerticalFlip))
				scaleMatrix = scaleMatrix * Matrix4.Scale(1, -1, 1);
			
			for(int i=0; i<boneCount; i++)
			{
				if (!currentAnimation.Keyframes[currentKeyframeIndex].Bones[i].Visible)
					continue;

				bool bvf = currentAnimation.Keyframes[currentKeyframeIndex].Bones[i].VerticalFlip;
				bool bhf = currentAnimation.Keyframes[currentKeyframeIndex].Bones[i].HorizontalFlip;

				spriteBatch.Draw(currentAnimation.Texture, Vector2.Zero, currentAnimation.TextureRegions[currentAnimation.Keyframes[currentKeyframeIndex].Bones[i].TextureIndex].Bounds,
					color, 0, currentAnimation.TextureRegions[currentAnimation.Keyframes[currentKeyframeIndex].Bones[i].TextureIndex].OriginCenter,
					new Vector2(bhf ? -1.0f : 1.0f, bvf ? -1.0f : 1.0f), currentAnimation.TextureRegions[currentAnimation.Keyframes[currentKeyframeIndex].Bones[i].TextureIndex].Rotated ? TextureEffects.RotatePackedUVs : TextureEffects.None,
					boneTransforms[i].Transform *
					scaleMatrix *
					Matrix4.CreateTranslation(-RotationOrigin.X, -RotationOrigin.Y, 0) *
					Matrix4.CreateRotationZ(rotation) *
					Matrix4.CreateTranslation(RotationOrigin.X, RotationOrigin.Y, 0) *
					Matrix4.CreateTranslation(position.X, position.Y, 0));
			}
		}

		public void AddAnimation(string name, DeminaAnimation deminaAnimation)
		{
			if(boneCount != -1 && boneCount != deminaAnimation.Keyframes[0].Bones.Count)
				throw new InvalidOperationException("Demina.AddAnimation " + name + " invalid bone count");
		
			animations[name] = deminaAnimation;
		
			if(boneCount == -1)
			{
				boneCount = deminaAnimation.Keyframes[0].Bones.Count;
				currentAnimation = deminaAnimation;
				CurrentAnimationName = name;
				currentAnimationTime = 0;
				currentKeyframeIndex = 0;
				
				boneTransforms.Clear();
				boneUpdateOrder.Clear();
				
				for(int i=0; i<boneCount; i++)
				{
					BoneTransform bt = new BoneTransform();
					bt.Rotation = 0;
					bt.Translation = Vector2.Zero;
					bt.Transform = Matrix4.Identity;
					
					BoneTransitionState bts = new BoneTransitionState();
					bts.Rotation = 0;
					bts.Translation = Vector2.Zero;

					boneTransforms.Add(bt);
					InsertUpdateOrderIndex(i);
					transitionStates.Add(bts);
				}
			}
		}
	
		protected void InsertUpdateOrderIndex(int boneIndex)
		{
			if(boneUpdateOrder.Contains(boneIndex))
				return;
			
			Bone bone = currentAnimation.Keyframes[0].Bones[boneIndex];
			if(bone.ParentIndex != -1)
				InsertUpdateOrderIndex(bone.ParentIndex);
			boneUpdateOrder.Add(boneIndex);
		}
	
		protected void UpdateBoneTransforms()
		{
			Keyframe lerpKeyframe;
			float t;
			float time = currentAnimationTime - currentAnimation.Keyframes[currentKeyframeIndex].FrameTime;
		
			if(transitioning)
			{
				lerpKeyframe = transitionAnimation.Keyframes[transitionKeyframe];
				t = transitionTime / transitionTotalTime;
				
				for(int i=0; i<boneCount; i++)
				{
					int currentBone = boneUpdateOrder[i];
				
					Bone lerpBone = lerpKeyframe.Bones[currentBone];
				
					boneTransforms[currentBone].Rotation = MathHelper.Lerp(transitionStates[currentBone].Rotation, lerpBone.Rotation, t);
					boneTransforms[currentBone].Translation = MathHelper.Lerp(transitionStates[currentBone].Translation, lerpBone.Position, t);
					Matrix4 parentMatrix = currentAnimation.Keyframes[currentKeyframeIndex].Bones[currentBone].ParentIndex == -1 ? Matrix4.Identity : boneTransforms[currentAnimation.Keyframes[currentKeyframeIndex].Bones[currentBone].ParentIndex].Transform;
					boneTransforms[currentBone].Transform =
						Matrix4.CreateRotationZ(boneTransforms[currentBone].Rotation) *
						Matrix4.CreateTranslation(boneTransforms[currentBone].Translation.X, boneTransforms[currentBone].Translation.Y, 0) *
						parentMatrix;

				}
			
				return;
			}
		
			if (currentKeyframeIndex == currentAnimation.Keyframes.Count - 1)
			{
				lerpKeyframe = currentAnimation.Keyframes[0];
			
				if (currentAnimation.LoopFrame != -1)
					t = time / (currentAnimation.LoopTime - currentAnimation.Keyframes[currentKeyframeIndex].FrameTime);
				else
					t = 0;
			}
			else
			{
				lerpKeyframe = currentAnimation.Keyframes[currentKeyframeIndex + 1];
				t = time / (lerpKeyframe.FrameTime - currentAnimation.Keyframes[currentKeyframeIndex].FrameTime);
			}
		
			for(int i=0; i<boneCount; i++)
			{
				int currentBone = boneUpdateOrder[i];
			
				Bone bone = currentAnimation.Keyframes[currentKeyframeIndex].Bones[currentBone];
				Bone lerpBone = lerpKeyframe.Bones[currentBone];
			
				boneTransforms[currentBone].Rotation = MathHelper.Lerp(bone.Rotation, lerpBone.Rotation, t);
				boneTransforms[currentBone].Translation = MathHelper.Lerp(bone.Position, lerpBone.Position, t);
				Matrix4 parentMatrix = currentAnimation.Keyframes[currentKeyframeIndex].Bones[currentBone].ParentIndex == -1 ? Matrix4.Identity : boneTransforms[currentAnimation.Keyframes[currentKeyframeIndex].Bones[currentBone].ParentIndex].Transform;
				boneTransforms[currentBone].Transform =
					Matrix4.CreateRotationZ(boneTransforms[currentBone].Rotation) *
					Matrix4.CreateTranslation(boneTransforms[currentBone].Translation.X, boneTransforms[currentBone].Translation.Y, 0) *
					parentMatrix;
			}
		}

		Dictionary<string, DeminaAnimation> animations = new Dictionary<string, DeminaAnimation>();
	
		int boneCount;
		
		DeminaAnimation currentAnimation;
		float currentAnimationTime;
		int currentKeyframeIndex;
		
		List<BoneTransform> boneTransforms = new List<BoneTransform>();
		List<int> boneUpdateOrder = new List<int>();
		
		bool transitioning;
		DeminaAnimation transitionAnimation;
		float transitionTime;
		float transitionTotalTime;
		int transitionKeyframe;
		string transitionTag;
		float transitionTagTime;
		string transitionTagAnimationName;
		List<BoneTransitionState> transitionStates = new List<BoneTransitionState>();
	}
}

